package checker

import (
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/source"
)


type funcUnifyResult interface { funcDesugarResult() }
func (funcUnifySuccess) funcDesugarResult() {}
type funcUnifySuccess struct {
	decl  *ast.DeclFunction
}
func (funcUnifyFailure) funcDesugarResult() {}
type funcUnifyFailure struct {
	err *source.Error
}
func (funcUnifyNonFunction) funcDesugarResult() {}
type funcUnifyNonFunction struct {}

func unifyFunction(stmt ast.Statement, mi *ModuleInfo, types TypeRegistry) funcUnifyResult {
	switch stmt := stmt.(type) {
	case ast.DeclFunction:
		return funcUnifySuccess { decl: &stmt }
	case ast.DeclConst:
		return funcUnifySuccess { decl: desugarConst(&stmt) }
	case ast.DeclMethod:
		var decl_, err = desugarMethod(&stmt, mi.ModName, types)
		if err != nil {
			return funcUnifyFailure { err: err }
		}
		return funcUnifySuccess { decl: decl_ }
	case ast.Entry:
		return funcUnifySuccess { decl: desugarEntry(&stmt) }
	default:
		return funcUnifyNonFunction {}
	}
}

func desugarConst(decl *ast.DeclConst) *ast.DeclFunction {
	var type_node = decl.Type.Node
	var unit_type = ast.UnitType(type_node)
	var value_node = decl.Value.Node
	var unit_pattern = ast.UnitPattern(value_node)
	var body ast.Body
	switch v := decl.Value.ConstValue.(type) {
	case ast.Expr:
		body = ast.Lambda {
			Node:   value_node,
			Input:  unit_pattern,
			Output: v,
		}
	case ast.NativeRef:
		body = v
	case ast.LoadedAsset:
		body = v
	case ast.InvalidAsset:
		body = v
	}
	return &ast.DeclFunction {
		Node:     decl.Node,
		Docs:     decl.Docs,
		Meta:     decl.Meta,
		Public:   decl.Public,
		Name:     decl.Name,
		Params:   nil,
		Implicit: ast.ReprRecord {},
		InOut:    ast.FuncInOut {
			Node: type_node,
			Repr: ast.ReprFunc {
				Node:   type_node,
				Input:  unit_type,
				Output: decl.Type,
			},
		},
		Body: ast.VariousBody {
			Node: value_node,
			Body: body,
		},
		Kind: ast.FK_Constant,
	}
}

func desugarMethod(decl *ast.DeclMethod, mod string, types TypeRegistry) (*ast.DeclFunction, *source.Error) {
	var recv = decl.Receiver
	var recv_item_name = ast.Id2String(recv)
	var recv_name = name.MakeTypeName(mod, recv_item_name)
	var recv_def, recv_def_exists = types[recv_name]
	if !(recv_def_exists) {
		return nil, source.MakeError(recv.Location,
			E_ReceiverTypeNotFound {
				TypeName: recv_name.String(),
			})
	}
	var recv_params = recv_def.AstNode.Params
	var recv_args = make([] ast.VariousType, len(recv_params))
	var params = make([] ast.FuncTypeParam, len(recv_params))
	for i, p := range recv_params {
		recv_args[i] = ast.VariousType {
			Node: recv.Node,
			Type: ast.TypeRef {
				Node: recv.Node,
				Item: p.Name,
			},
		}
		params[i] = ast.FuncTypeParam {
			Node: p.Node,
			Name: p.Name,
		}
	}
	var recv_t = ast.VariousType {
		Node: recv.Node,
		Type: ast.TypeRef {
			Node:     recv.Node,
			Item:     recv,
			TypeArgs: recv_args,
		},
	}
	return &ast.DeclFunction {
		Node:     decl.Node,
		Docs:     decl.Docs,
		Meta:     decl.Meta,
		Public:   decl.Public,
		Name:     decl.Name,
		Params:   params,
		Implicit: ast.ReprRecord {},
		InOut:    ast.FuncInOut {
			Node: decl.Type.Node,
			Repr: ast.ReprFunc {
				Node:   decl.Type.Node,
				Input:  recv_t,
				Output: decl.Type,
			},
		},
		Body: decl.Body,
		Kind: ast.FK_Method,
	}, nil
}

func desugarEntry(decl *ast.Entry) *ast.DeclFunction {
	var body = ast.VariousBody {
		Node: decl.Node,
		Body: ast.Lambda {
			Node:   decl.Node,
			Input:  ast.UnitPattern(decl.Node),
			Output: decl.Effect,
		},
	}
	return &ast.DeclFunction {
		Node:     decl.Node,
		Docs:     [] ast.Doc {},
		Meta:     ast.Meta {},
		Public:   false,
		Name:     ast.String2Id(EffectFunctionName, decl.Node),
		Params:   [] ast.FuncTypeParam {},
		Implicit: ast.ReprRecord {},
		InOut:    ast.FuncInOut {}, // intended empty (special handling)
		Body:     body,
		Kind:     ast.FK_Entry,
	}
}


